package com.example.browseimagedemo

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateCentroid
import androidx.compose.foundation.gestures.calculateCentroidSize
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateRotation
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChanged
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.times
import androidx.compose.ui.res.painterResource
import kotlin.math.PI
import kotlin.math.abs

@Composable
fun ImageView(
  painter: Painter = painterResource(id = R.drawable.pig),
  contentDescription: String? = "pig",
  contentScale: ContentScale = ContentScale.Fit,
) {
  var srcSize by remember { mutableStateOf(Size.Zero) }
  var dstSize by remember { mutableStateOf(Size.Zero) }

  var scale by remember { mutableStateOf(1f) }
  var offset by remember { mutableStateOf(Offset.Zero) }

  Image(
    painter = painter,
    contentDescription = contentDescription,
    contentScale = contentScale,
    modifier = Modifier
      .fillMaxSize()
      .clip(RectangleShape)
      .background(color = Color.Black)
      .pointerInput(Unit) {
        detectTransformGesturesMy { _, panChange, zoomChange, _ ->
          scale = maxOf(0.5f, minOf(scale * zoomChange, 5f))
          val oldOffset = offset
          offset = Offset(
            adjust(scale * srcSize.width, dstSize.width, offset.x + panChange.x),
            adjust(scale * srcSize.height, dstSize.height, offset.y + panChange.y)
          )
          zoomChange != 1f || offset.x != oldOffset.x
        }
      }
      .drawBehind {
        dstSize = size
        srcSize = painter.intrinsicSize *
                contentScale.computeScaleFactor(painter.intrinsicSize, dstSize)
      }
      .graphicsLayer(
        scaleX = scale,
        scaleY = scale,
        translationX = offset.x,
        translationY = offset.y
      )
  )
}

private fun adjust(inSize: Float, outSize: Float, offset: Float): Float {
  if (inSize > outSize) {
    val maxOffset = 0.5f * (inSize - outSize)
    if (offset > maxOffset) return maxOffset
    val minOffset = 0.5f * (outSize - inSize)
    if (offset < minOffset) return minOffset
    return offset
  } else {
    return 0f
  }
}

// from std src
private suspend fun PointerInputScope.detectTransformGesturesMy(
  panZoomLock: Boolean = false,
  onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Boolean
) {
  awaitEachGesture {
    var rotation = 0f
    var zoom = 1f
    var pan = Offset.Zero
    var pastTouchSlop = false
    val touchSlop = viewConfiguration.touchSlop
    var lockedToPanZoom = false

    awaitFirstDown(requireUnconsumed = false)
    do {
      val event = awaitPointerEvent()
      val canceled = event.changes.any { it.isConsumed }
      if (!canceled) {
        val zoomChange = event.calculateZoom()
        val rotationChange = event.calculateRotation()
        val panChange = event.calculatePan()

        if (!pastTouchSlop) {
          zoom *= zoomChange
          rotation += rotationChange
          pan += panChange

          val centroidSize = event.calculateCentroidSize(useCurrent = false)
          val zoomMotion = abs(1 - zoom) * centroidSize
          val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)
          val panMotion = pan.getDistance()

          if (zoomMotion > touchSlop ||
            rotationMotion > touchSlop ||
            panMotion > touchSlop
          ) {
            pastTouchSlop = true
            lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
          }
        }

        if (pastTouchSlop) {
          var shouldConsume = true
          val centroid = event.calculateCentroid(useCurrent = false)
          val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
          if (effectiveRotation != 0f ||
            zoomChange != 1f ||
            panChange != Offset.Zero
          ) {
            shouldConsume = onGesture(centroid, panChange, zoomChange, effectiveRotation)
          }
          if (shouldConsume) {
            event.changes.forEach {
              if (it.positionChanged()) {
                it.consume()
              }
            }
          }
        }
      }
    } while (!canceled && event.changes.any { it.pressed })
  }
}